package pt.uma.gap.accessperm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;

import pt.uma.gap.ast.AssignmentInfo;
import pt.uma.gap.ast.ClassASTInfo;
import pt.uma.gap.ast.DeclarationASTInfo;
import pt.uma.gap.ast.FieldASTInfo;
import pt.uma.gap.ast.GAPVisitor;
import pt.uma.gap.ast.MethodASTInfo;
import pt.uma.gap.ast.ParameterASTInfo;
import pt.uma.gap.graph.ClassGraphGenerator;
import pt.uma.gap.graph.ClassNode;
import pt.uma.gap.graph.MethodNode;
import pt.uma.gap.graph.NullEdgeNodeException;

/**
 * Generate access permission
 * 
 * @author Duarte Abreu
 * 
 */
public class GenAccessPermissionCUAnalysis {
	
	private static Logger logger = Logger.getLogger(ClassGraphGenerator.class.getName());

	/**
	 * This can be considered as a main function Generate Access Permission for
	 * all compilation unit. Load AST Node from compilation unit. Generate Graph
	 * in class structure, from class convert to XML and DOT Language.
	 * 
	 * @param compilationUnitList
	 * @param contextType
	 *            Read-Context = 0; Read-Write-Context = 1
	 * @throws Exception
	 * 
	 * 
	 */
	public void analyzeCompilationUnit(
			List<ICompilationUnit> compilationUnitList, int contextType) {

		// List<ClassASTInfo> allClassASTInfo = new ArrayList<ClassASTInfo>();
		try {
			List<ClassNode> allClassNodeList = new ArrayList<ClassNode>();
			for (ICompilationUnit cunit : compilationUnitList) {

				CompilationUnit compilationUnit = (CompilationUnit) pt.uma.gap.accessperm.WorkspaceUtilities
						.getASTNodeFromCompilationUnit(cunit);

				GAPVisitor visitor = new GAPVisitor();
				// Run all AST Node
				compilationUnit.accept(visitor);
				// Debug - Print to Console
				print(visitor);

				createNodes(contextType, allClassNodeList, visitor);
			}
			// store the full names of methods that includes pkg,class and
			// methodswithParametrestypes
			HashMap<String, MethodNode> methodNodeMap = mapWithFullMethodName(allClassNodeList);

			// Map method calls with methods node
			ClassGraphGenerator.getInstance().mapCallingMethods(allClassNodeList,
					methodNodeMap);

			createEdges(contextType, allClassNodeList, methodNodeMap);

			createDOTfromClassStructure(allClassNodeList);
			
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Generatin Access Permission failure", e);
			e.printStackTrace();
		}

	}

	private void createEdges(int contextType, List<ClassNode> allClassNodeList,
			HashMap<String, MethodNode> methodNodeMap) throws Exception {
		 // we are adding edges to allClassNodeList inside this method
			ClassGraphGenerator.getInstance().generateGraph(allClassNodeList,
					contextType, methodNodeMap);

	}

	private void createNodes(int contextType, List<ClassNode> allClassNodeList,
			GAPVisitor visitor) throws NullEdgeNodeException {

		List<ClassASTInfo> classInfoList = visitor.getClassInfoList();
		
		// This list contains class fields, parametres and local varaibles
		// of a method
		List<ClassNode> list = ClassGraphGenerator.getInstance()
				.generateBasicGraph(classInfoList, contextType);
		allClassNodeList.addAll(list);

	}

	private void createDOTfromClassStructure(List<ClassNode> allClassNodeList) {
		try {
			DOTGraphTransformer dOTGraphTransformer = new DOTGraphTransformer();
			dOTGraphTransformer.transformeGraph(allClassNodeList);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Create DOT from Class Structure failure", e);
			e.printStackTrace();
		}
	}

	private HashMap<String, MethodNode> mapWithFullMethodName(
			List<ClassNode> allClassNodeList) {
		// Map method with his complete path/name. This information is used in
		// method invocations and methods call
		HashMap<String, MethodNode> methodNodeMap = new HashMap<String, MethodNode>();
		for (ClassNode classNode : allClassNodeList) {
			for (MethodNode methodNode : classNode.getMethodNodeList()) {
				String key = classNode.getFullName() + "."
						+ methodNode.getCompleteName();
				methodNodeMap.put(key, methodNode);
			}
		}
		return methodNodeMap;
	}

	private void print(GAPVisitor visitor) {
		log("#################################################################################");

		for (ClassASTInfo classInfo : visitor.getClassInfoList()) {
			log("------------------------------------------------------------");
			log("Class: " + classInfo.getClassName());
			log("Package: " + classInfo.getPackageName());
			log("");

			for (FieldASTInfo fieldInfo : classInfo.getFieldInfoList()) {
				log("Field: ", fieldInfo.getVariableDeclaration().getName()
						+ ", Type: "
						+ fieldInfo.getFieldDeclaration().getType()
						+ ", Initializer: "
						+ fieldInfo.getVariableDeclaration().getInitializer());
			}

			for (MethodASTInfo method : classInfo.getMethodInfoList()) {
				log("\n Method: " + method.getMethodName() + ", Return: "
						+ method.getReturnType());

				for (ParameterASTInfo parameterInfo : method
						.getParametersList()) {
					log(1, "-> Parameter: ", parameterInfo.getName()
							+ ", Type: " + parameterInfo.getType());

				}

				// printByGroup(method);

				printBySequence(method);

			}
		}

		log("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
	}

	@SuppressWarnings("unused")
	private void printByGroup(MethodASTInfo method) {
		for (DeclarationASTInfo variableInfo : method.getVariableInfoList()) {
			printVariableDeclaration(variableInfo);
		}
		for (AssignmentInfo assignmentInfo : method.getAssignmentInfoList()) {
			printAssignment(assignmentInfo);
		}
		for (MethodInvocation methodInvocation : method
				.getMethodInvocationList()) {
			printMethodInvocation(0, methodInvocation);
		}
	}

	private void printBySequence(MethodASTInfo method) {
		for (Object obj : method.getActionList()) {
			if (obj instanceof DeclarationASTInfo) {
				printVariableDeclaration((DeclarationASTInfo) obj);
			} else if (obj instanceof AssignmentInfo) {
				printAssignment((AssignmentInfo) obj);
			} else if (obj instanceof MethodInvocation) {
				printMethodInvocation(0, (MethodInvocation) obj);
			}
		}
	}

	private void printMethodInvocation(int pos,
			MethodInvocation methodInvocation) {
		String name = null;
		log("");
		log(pos + 1, "-> MethodInvocation - METHOD CALL RULE ");

		log(pos + 2, "expression: ", methodInvocation.getExpression());
		log(pos + 2, "getName: ", methodInvocation.getName());
		// log(pos+2, "resolveConstantExpressionValue: ",
		// methodInvocation.resolveConstantExpressionValue());
		log(pos + 2, "resolveMethodBinding: ",
				methodInvocation.resolveMethodBinding());
		if (methodInvocation.resolveMethodBinding() != null) {
			log(pos + 3, "getName: ", methodInvocation.resolveMethodBinding()
					.getName());
			log(pos + 3, "getKey: ", methodInvocation.resolveMethodBinding()
					.getKey());

			for (ITypeBinding typeBinding : methodInvocation
					.resolveMethodBinding().getParameterTypes()) {
				log(pos + 4, "Parameter BinaryName: ",
						typeBinding.getBinaryName());
				log(pos + 4, "Parameter Name: ", typeBinding.getName());
				log(pos + 4, "Parameter QualifiedName: ",
						typeBinding.getQualifiedName());
				if (name == null) {
					name = typeBinding.getName();
				} else {
					name += "," + typeBinding.getName();
				}
			}

			log("");
			ITypeBinding returnTypeBinding = methodInvocation
					.resolveMethodBinding().getReturnType();
			log(pos + 4, "Return BinaryName: ",
					returnTypeBinding.getBinaryName());
			log(pos + 4, "Return Name: ", returnTypeBinding.getName());
			log(pos + 4, "Return QualifiedName: ",
					returnTypeBinding.getQualifiedName());
			log("");
			ITypeBinding declaringTypeBinding = methodInvocation
					.resolveMethodBinding().getDeclaringClass();
			log(pos + 4, "Declaring BinaryName: ",
					declaringTypeBinding.getBinaryName());
			log(pos + 4, "Declaring Name: ", declaringTypeBinding.getName());
			log(pos + 4, "Declaring QualifiedName: ",
					declaringTypeBinding.getQualifiedName());

			name = declaringTypeBinding.getQualifiedName() + "."
					+ methodInvocation.getName() + "(" + name + ")";
			log("");
			log(pos + 2, "name: " + name);
			log("");
		}

		// log(pos+2, "resolveTypeBinding: ",
		// methodInvocation.resolveTypeBinding());

		for (Object expression : methodInvocation.arguments()) {
			log(pos + 2, "argument expression: ", expression);
		}
	}

	private void printAssignment(AssignmentInfo assignmentInfo) {
		log("");
		log(1, "-> Assignment: ", assignmentInfo.getAssignment().getOperator());

		log(2, "getAssignmentType(): ", assignmentInfo.getAssignmentType());
		log(2, "LeftHandSide(): ", assignmentInfo.getAssignment()
				.getLeftHandSide());
		log(2, "RightHandSide(): ", assignmentInfo.getAssignment()
				.getRightHandSide());
		// log(2, "resolveTypeBinding().getName(): ",
		// assignmentInfo.getAssignment().resolveTypeBinding().getName());
		// log(2, "resolveTypeBinding().getBinaryName(): ",
		// assignmentInfo.getAssignment().resolveTypeBinding().getBinaryName());
		log(2, "resolveTypeBinding().getQualifiedName(): ", assignmentInfo
				.getAssignment().resolveTypeBinding().getQualifiedName());
		if (assignmentInfo.getMethodInvocationList() != null) {
			for (MethodInvocation methodInvocation : assignmentInfo
					.getMethodInvocationList()) {
				printMethodInvocation(1, methodInvocation);
			}
		}
		// log(2, "resolveConstantExpressionValue(): ",
		// assignmentInfo.getAssignment().resolveConstantExpressionValue());
	}

	private void printVariableDeclaration(DeclarationASTInfo variableInfo) {
		log("");
		log(1, "-> VariableDeclaration: ",
				variableInfo.getName() + ", Type: " + variableInfo.getType()
						+ ", Initializer: " + variableInfo.getInitializer());

		log(2, "getDeclarationType(): ", variableInfo.getDeclarationType());
		if (variableInfo.getMethodInvocationList() != null) {
			for (MethodInvocation methodInvocation : variableInfo
					.getMethodInvocationList()) {
				printMethodInvocation(1, methodInvocation);
			}
		}
	}

	public void log(Object string) {
		System.out.println(string);
	}

	public void log(String string, Object name) {
		log(string + " " + name);
	}

	public void log(int pos, String string, Object name) {
		String aux = "";
		for (int i = 0; i < pos; i++) {
			aux += "\t";
		}
		log(aux + string, name);
	}

	public void log(int pos, String string) {
		String aux = "";
		for (int i = 0; i < pos; i++) {
			aux += "\t";
		}
		log(aux + string);
	}

}
